package jeeobserver.server.database;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

class DatabaseFile<T extends DatabaseFile.DatabaseEntity> {

	public static final byte STATUS_NEW = 1;

	public static final byte STATUS_OPEN = 2;

	public static final byte STATUS_CLOSED = 3;

	protected RandomAccessFile file;

	protected String name;

	private FileLock fileLock;

	protected Class<T> type;

	protected int recordLenght;

	public void setType(Class<T> type) {
		this.type = type;
	}

	public DatabaseFile(String name, Class<T> type, int recordLenght) throws IOException {

		this.file = new RandomAccessFile(name, "rwd");

		this.name = name;

		this.type = type;

		this.recordLenght = recordLenght;

		//TODO
		//this.fileLock = this.file.getChannel().tryLock();

		if (this.file.length() < HeaderEntity.BYTES_SIZE) {
			this.file.write(new HeaderEntity().getBytes());
		}

		this.file.seek(0);
		this.file.write(STATUS_OPEN);

		this.file.seek(0);
	}

	public void reset() throws IOException {
		this.file.seek(HeaderEntity.BYTES_SIZE);
	}

	public void rename(String destination) throws IOException {
		this.close();
		new File(this.name).renameTo(new File(destination));
	}

	public void delete() throws IOException {
		this.close();
		new File(this.name).delete();
	}

	public void clear() throws IOException {
		this.file.setLength(HeaderEntity.BYTES_SIZE);
	}

	public synchronized List<T> readAll() throws IOException {

		this.reset();

		List<T> results = new ArrayList<T>();

		T entity;

		while ((entity = this.read()) != null) {
			results.add(entity);
		}

		return results;
	}

	public synchronized <type> T read() throws IOException {
		try {
			//entityClass entity = (entityClass) entityClass.getConstructor(String.class).newInstance();
			//entityClass entity = (entityClass) entityClass.newInstance();

			byte bytes[];
			if (recordLenght > 0) {
				bytes = new byte[recordLenght];
			} else {
				if (this.file.length() - this.file.getFilePointer() > 4) {
					bytes = new byte[this.file.readInt()];
					//this.file.seek(this.file.getFilePointer() - 4);
				} else {
					bytes = new byte[0];
				}
			}

			if (bytes.length > 0 && this.file.read(bytes) == bytes.length) {
				//((DatabaseEntity) entity).setBytes(bytes);
				//entity = (entityClass)
				//ByteBuffer buffer = ByteBuffer.wrap(bytes);
				return type.getConstructor(byte[].class).newInstance(bytes);
			}
		} catch (Exception e) {
			throw new IOException(e);
		}

		return null;
	}

	public synchronized void write(Set<T> entities) throws IOException {

		int bufferSize = 0;

		if (recordLenght == 0) {
			for (T entity : entities) {
				bufferSize = bufferSize + 4 + entity.getSize();
			}
		} else {
			bufferSize = entities.size() * this.recordLenght;
		}

		ByteBuffer buffer = ByteBuffer.allocate(bufferSize);

		for (T entity : entities) {
			if (this.recordLenght == 0) {
				this.file.writeInt(entity.getBytes().length);
			}

			buffer.put(entity.getBytes());
		}

		this.file.write(buffer.array());
	}

	public synchronized void write(T entity) throws IOException {
		if (recordLenght == 0) {
			this.file.writeInt(entity.getBytes().length);
		}
		this.file.write(entity.getBytes());
	}

	public synchronized void writePervious(T entity) throws IOException {
		this.file.seek(this.file.getFilePointer() - entity.getSize());
		this.file.write(entity.getBytes());
	}

	public synchronized long append(T entity) throws IOException {

		this.file.seek(this.file.length());

		long position = this.file.getFilePointer();

		this.write(entity);

		return position;
	}

	public byte getStatus() throws IOException {
		this.file.seek(0);

		return this.file.readByte();
	}

	public byte[] getVersion() throws IOException {
		this.file.seek(1);

		byte version[] = new byte[3];

		this.file.read(version);

		return version;
	}

	protected void close() throws IOException {

		if (this.fileLock != null) {
			this.fileLock.release();
		}

		this.file.seek(0);
		this.file.write(STATUS_CLOSED);

		this.file.close();
	}

	static class DatabaseFileFixedRecord<T> extends DatabaseFile {

		public DatabaseFileFixedRecord(String path, Class<T> type, int recordLenght) throws IOException {
			super(path, type, recordLenght);
		}
	}

	static class DatabaseFileVariableRecord<T> extends DatabaseFile {

		public DatabaseFileVariableRecord(String path, Class<T> type) throws IOException {
			super(path, type, 0);
		}
	}

	public interface DatabaseEntity {

		public byte[] getBytes();

		public int getSize();
	}

	public static class HeaderEntity implements DatabaseEntity {

		public static final int BYTES_SIZE = 1024;

		private byte status;

		private Date creationDate;

		private long size;

		public HeaderEntity(ByteBuffer buffer) {
			this.status = buffer.get();
			this.creationDate = new Date(buffer.getLong());
		}

		public HeaderEntity(byte[] bytes) {
			this(ByteBuffer.wrap(bytes));
		}

		public HeaderEntity() {
			this.status = 0;
			this.creationDate = new Date();
		}

		public byte[] getBytes() {

			ByteBuffer buffer = ByteBuffer.allocate(BYTES_SIZE);

			buffer.put(this.status);

			buffer.putLong(this.creationDate.getTime());

			return buffer.array();
		}

		public int getSize() {
			return BYTES_SIZE;
		}

		public byte getStatus() {
			return status;
		}

		public Date getCreationDate() {
			return creationDate;
		}

		public void setStatus(byte status) {
			this.status = status;
		}

		public void setCreationDate(Date creationDate) {
			this.creationDate = creationDate;
		}
	}
}
